// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Defines the functions and data structures used to create and
 * control an extensions's browser action.
 *
 * A browser action is a way for extensions to attach a function to the browser
 * interface.  A browser action is represented by an icon (Chrome: toolbar,
 * right of the url bar; Firefox: statusbar, right side) and optionally by a
 * tooltip, badge, and/or popup.
 *
 * An icon is a 19x19 image that when clicked will cause the associated popup
 * html to appear, or send a message signifying the action was taken if the
 * popup was not supplied.
 *
 * A tooltip is a single string that will display when the icon has the mouse
 * hovering over it.
 *
 * A badge is a single string that consists of no more than four characters.
 * It is displayed over top of the icon in Chrome, but under the icon in
 * Firefox.
 *
 * A popup is specified as html and the window will popup over the content.
 *
 * Finally, if the browser icon is right-clicked then it will display a
 * context menu for controlling the extension associated with that action.
 *
 * This was a brief overview of a browser action, so for more details please
 * see the Chrome Extension website:
 *     http://code.google.com/chrome/extensions/browserAction.html
 *
 * @author jasonstredwick@google.com (Jason Stredwick)
 */


goog.provide('mimic.BrowserAction');

goog.require('bite.common.signal.Simple');
goog.require('console');
goog.require('mimic.WindowManager');



/**
 * Create an instance to manage a Chrome Extension's browser action listeners.
 * @constructor
 */
mimic.BrowserAction = function() {
  /**
   * Handles onClicked actions applied to the browser action.
   * @type {!bite.common.signal.Simple}
   */
  this.onClicked = new bite.common.signal.Simple();
};


/**
 * Clean up the BrowserAction object before deletion.
 */
mimic.BrowserAction.prototype.destroy = function() {
  this.onClicked.clear();
};


/**
 * Defines the browser action XUL DOM Element base id.  The actual id is
 * the concatenation of the base id and the extension's name.
 * @type {string}
 * @private
 */
mimic.BrowserAction.BROWSER_ACTION_ID_ = 'mimic.browserAction.';


/**
 * Defines the browser action's menu base id.  The actual id is the
 * concatenation of the base id and the extension's name.
 * @type {string}
 * @private
 */
mimic.BrowserAction.MENU_ID_ = 'mimic.browserAction.menu.';


/**
 * Defines the browser action template name.  This name is used with the
 * base id to lookup the template extension element for cloning.
 * @type {string}
 * @private
 */
mimic.BrowserAction.TEMPLATE_MASTER_ID_ = 'templateMaster';


/**
 * Defines the tagnames for the children Elements of the browser action.
 * @enum {string}
 * @private
 */
mimic.BrowserAction.ChildTagname_ = {
  BADGE: 'label',
  CONTEXT_MENU: 'menupopup',
  ICON: 'image'
};


/**
 * Creates the browser action DOM Element and adds it to the status bar.
 * @param {string} alias The alias for the extension.
 * @param {!XPCNativeWrapper} doc The wrapped XUL Document for a ChromeWindow.
 * @return {Element} The new browser action element or null upon failure.
 * @private
 */
mimic.BrowserAction.create_ = function(alias, doc) {
  // Check to see if the browser action already exists and return it if it
  // does.
  var action = mimic.BrowserAction.get_(alias, doc);
  if (action) {
    return action;
  }

  // Retrieve the browser's statusbar DOM Element.
  var statusbar = doc.getElementById('status-bar');
  if (!statusbar) {
    return null;
  }

  // Retrieve the browser action DOM Element template.
  var templateId = mimic.BrowserAction.BROWSER_ACTION_ID_ +
                   mimic.BrowserAction.TEMPLATE_MASTER_ID_;
  var templateMaster = doc.getElementById(templateId);
  if (!templateMaster) {
    return null;
  }

  // Since there is no browser action yet, create the DOM Element by cloning
  // the browser icon template.
  action = templateMaster.cloneNode(true);
  if (!action) {
    return null;
  }

  // Retrieve the children.
  var menu = action.querySelector('menupopup');
  if (!menu || !mimic.BrowserAction.initContextMenu_(alias, menu)) {
    return null;
  }

  // Set properties of the browser action DOM Element.
  action.id = mimic.BrowserAction.BROWSER_ACTION_ID_ + alias;
  action.contextMenu = mimic.BrowserAction.MENU_ID_ + alias;
  //action.setAttribute('role', 'button');

  statusbar.appendChild(action);

  return action;
};


/**
 * Destroys the extension's browser action by removing the extension's
 * icon and cleaning up it relationships.
 * @param {string} alias The extension's alias.
 * @param {!XPCNativeWrapper} doc The wrapped XUL Document for a ChromeWindow.
 */
mimic.BrowserAction.destroy = function(alias, doc) {
  // Find the browser action XUL DOM Element if it exists.
  var element = mimic.BrowserAction.get_(alias, doc);
  if (!element) {
    return;
  }

  // Remove the element (and all its children) from the DOM.  Since there are
  // no other relationships, this should suffice for now.
  element.parentNode.removeChild(element);
};


/**
 * Destroys all browser actions for the given extension across all windows.
 * @param {string} alias The extension's alias.
 */
mimic.BrowserAction.destroyWindows = function(alias) {
  var windows = mimic.WindowManager.getInstance().getAllChromeWindows();
  for (var i = 0; i < windows.length; ++i) {
    var doc = windows[i].document;
    if (!doc) {
      continue;
    }

    mimic.BrowserAction.destroy(alias, doc);
  }
};


/**
 * Retrieves the XUL DOM Element based on an optional tagname.  If a tagname is
 * not given then it is assumed that the browser action element is desired.
 * Otherwise, the specified child will be retrieved.
 * Note that query selector using an id does not work for XUL Elements, so
 * the query selector for the child elements must be by tagname.
 * @param {string} alias The extension's alias.
 * @param {!XPCNativeWrapper} doc The wrapped XUL Document for a ChromeWindow.
 * @param {mimic.BrowserAction.ChildTagname_=} opt_tagname The child's
 *     tagname.
 * @return {Element} The DOM Element or null if not found.
 * @private
 */
mimic.BrowserAction.get_ = function(alias, doc, opt_tagname) {
  var actionId = mimic.BrowserAction.BROWSER_ACTION_ID_ + alias;
  var action = doc.getElementById(actionId);

  if (action && opt_tagname) {
    return action.querySelector(opt_tagname);
  }

  return action;
};


/**
 * Retrieves the XUL DOM Element based on an optional tagname.  If tagname is
 * not given then it is assumed that the browser action element is desired.
 * Otherwise, the specified child will be retrieved.  If the browser action has
 * not yet been created it will also try to create it.
 * Note that query selector using an id does not work for XUL Elements, so
 * the query selector for the child elements must be by tagname.
 * @param {string} alias The extension's alias.
 * @param {!XPCNativeWrapper} doc The wrapped XUL Document for a ChromeWindow.
 * @param {mimic.BrowserAction.ChildTagname_=} opt_tagname The child's
 *     tagname.
 * @return {Element} The DOM Element or null if not found.
 * @private
 */
mimic.BrowserAction.getOrCreate_ = function(alias, doc, opt_tagname) {
  var action = mimic.BrowserAction.create_(alias, doc);
  if (!action) {
    return null;
  }

  if (opt_tagname) {
    return action.querySelector(opt_tagname);
  }

  return action;
};


/**
 * Hides the browser action if hidden and appropriate.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 */
mimic.BrowserAction.hide = function(alias, actionObj) {
  if (actionObj) {
    actionObj.visible = false;
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Takes the initial manifest values and converts them to key/value pairs used
 * by these functions.
 * @param {string} alias The extension's alias.
 * @param {string} rootUrl The rootUrl for the extension.
 * @param {Object} actionObj The browserAction object.
 */
mimic.BrowserAction.init = function(alias, rootUrl, actionObj) {
  if (!actionObj) {
    return;
  }

  if ('default_icon' in actionObj) {
    actionObj.path = rootUrl + actionObj['default_icon'];
  }
  if ('default_title' in actionObj) {
    actionObj.title = actionObj['default_title'];
  }
  if ('default_popup' in actionObj) {
    actionObj.popup = rootUrl + actionObj['default_popup'];
  }
};


/**
 * Initializes the context menu for a newly cloned browserAction.
 * @param {string} alias The extension's alias.
 * @param {!Element} menu The menu to initialize.
 * @return {boolean} Whether or not the initialization was a success.
 * @private
 */
mimic.BrowserAction.initContextMenu_ = function(alias, menu) {
  menu.id = mimic.BrowserAction.MENU_ID_ + alias;

  var items = menu.querySelectorAll('menuitem');
  if (items) {
    items[0].setAttribute('label', alias);
  }

  return true;
};


/**
 * Sets the background color for the badge text.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {Object} data Object containing an array of numbers that will be
 *     converted to an RGBA color.  Thus only up to the first four values are
 *     used.  Also, each number is valued between 0..255.
 */
mimic.BrowserAction.setBadgeBackgroundColor = function(alias, actionObj,
                                                       data) {
  if (data) {
    var colors = data['color'] || [];

    // Load up each of the color components or default that component to 255
    // if not present.  The overall default RGBA is (255, 0, 0, 255) (red).
    var red = ((colors[0] || colors[0] == 0) ? colors[0] : 255) % 256;
    var green = ((colors[1] || colors[1] == 0) ? colors[1] : 0) % 256;
    var blue = ((colors[2] || colors[2] == 0) ? colors[2] : 0) % 256;
    var alpha = ((colors[3] || colors[3] == 0) ? colors[3] : 255) % 256;

    var color = 'rgba(' + red + ',' + green + ',' + blue + ',' + alpha + ')';

    actionObj.badgeBGColor = color;
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Sets the badge text for the browser action.  Only the first four characters
 * will be displayed.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {Object} data Object containing the text.
 */
mimic.BrowserAction.setBadgeText = function(alias, actionObj, data) {
  if (data) {
    actionObj.badgeText = data['text'] ? data['text'].substr(0, 4) : '';
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Sets the color for the badge text.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {Object} data Object containing an array of numbers that will be
 *     converted to an RGBA color.  Thus only up to the first four values are
 *     used.  Also, each number is valued between 0..255.
 */
mimic.BrowserAction.setBadgeTextColor = function(alias, actionObj, data) {
  if (data) {
    var colors = data['color'] || [];

    // Load up each of the color components or default that component to 255
    // if not present.  The overall default RGBA is (255, 255, 255, 255)
    // (white).
    var red = ((colors[0] || colors[0] == 0) ? colors[0] : 255) % 256;
    var green = ((colors[1] || colors[1] == 0) ? colors[1] : 255) % 256;
    var blue = ((colors[2] || colors[2] == 0) ? colors[2] : 255) % 256;
    var alpha = ((colors[3] || colors[3] == 0) ? colors[3] : 255) % 256;

    var color = 'rgba(' + red + ',' + green + ',' + blue + ',' + alpha + ')';

    actionObj.badgeTextColor = color;
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Sets the icon's image src.  This also creates a "Browser Action" by creating
 * the icon if it does not exist.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {string} rootUrl The rootUrl for the extension.
 * @param {Object} data An object containing the relative path to the image
 *     file "within" the extension.  If null is given, that means the icon is
 *     to be destroyed and thus the "Browser Action" along with it.
 */
mimic.BrowserAction.setIcon = function(alias, actionObj, rootUrl, data) {
  if (data) {
    actionObj.path = data['path'] ? rootUrl + data['path'] : '';
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Creates a popup div that can display an html document.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {Object} data An object containing the HTML url that is displayed as
 *     the popup.
 */
mimic.BrowserAction.setPopup = function(alias, actionObj, data) {
  if (data) {
    actionObj.popup = data['popup'] || '';
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Sets the title of the extension; i.e. the tooltip.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {Object} data An object containing the new title.
 */
mimic.BrowserAction.setTitle = function(alias, actionObj, data) {
  if (data) {
    actionObj.title = data['title'] || '';
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Shows the browser action if hidden and appropriate.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 */
mimic.BrowserAction.show = function(alias, actionObj) {
  if (actionObj) {
    actionObj.visible = true;
    mimic.BrowserAction.updateWindows_(alias, actionObj);
  }
};


/**
 * Update a given window's browser action if appropriate.  If there should be
 * a browser action but there isn't one then create a new one.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @param {!XPCNativeWrapper} doc The wrapped XUL Document for a ChromeWindow.
 */
mimic.BrowserAction.updateWindow = function(alias, actionObj, doc) {
  if (!actionObj) {
    return;
  }

  var childId = mimic.BrowserAction.ChildTagname_;

  var action = mimic.BrowserAction.getOrCreate_(alias, doc);
  var icon = mimic.BrowserAction.get_(alias, doc, childId.ICON);
  var badge = mimic.BrowserAction.get_(alias, doc, childId.BADGE);
  if (!action || !icon || !badge) {
    return;
  }

  if (actionObj.visible) {
    action.setAttribute('hidden', 'false');
    actionObj.path ? (icon.src = actionObj.path) : icon.removeAttribute('src');
    action.tooltipText = actionObj.title || '';

    // If there is text to set, then set the icon's label to be up to the first
    // four characters in the string.  If the text is the empty string, set the
    // label to the empty string; clearing the badge text.
    badge.style.backgroundColor = actionObj.badgeBGColor ||
                                  'rgba(255, 0, 0, 255)';
    badge.style.color = 'rgba(255, 255, 255, 255)'; // actionObj.badgeTextColor
    actionObj.badgeText ? (badge.value = actionObj.badgeText) :
                          badge.removeAttribute('value');

    // TODO (jasonstredwick): Add popup handling.
  } else {
    action.setAttribute('hidden', 'true');
  }
};


/**
 * Update all the browser actions across all windows after a property has
 * changed.
 * @param {string} alias The extension's alias.
 * @param {Object} actionObj The browserAction object.
 * @private
 */
mimic.BrowserAction.updateWindows_ = function(alias, actionObj) {
  var windows = mimic.WindowManager.getInstance().getAllChromeWindows();
  for (var i = 0; i < windows.length; ++i) {
    var doc = windows[i].document;
    if (!doc) {
      continue;
    }

    mimic.BrowserAction.updateWindow(alias, actionObj, doc);
  }
};

